/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.cdp.web.component.metadata.serializer;

import org.springframework.beans.BeanUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author Kaixuan Shi
 * @version 0.1
 */
public class CompareUtil {

    private static List<String> baseTypes = Arrays.asList(
            String.class.getName(),
            Integer.class.getName(),
            Long.class.getName(),
            Double.class.getName(),
            Float.class.getName(),
            BigDecimal.class.getName(),
            Date.class.getName(),
            Timestamp.class.getName(),
            LocalDate.class.getName(),
            LocalDateTime.class.getName(),
            Boolean.class.getName(),
            Short.class.getName(),
            Byte.class.getName(),
            Character.class.getName(),
            BigInteger.class.getName()
    );

    public static boolean equals(Object p1, Object p2) {
        if (p1 == p2) {
            return true;
        } else if (p1 != null && p2 != null) {
            Class cls = p1.getClass();
            if (!p1.getClass().equals(p2.getClass())) {
                System.out.println(p1.getClass().getName() + "," + p2.getClass().getName());
                return false;
            }
            //如果是基础类型数据
            if (baseTypes.contains(cls.getName())) {
                return Objects.equals(p1, p2);
            }
            PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(cls);
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                Method readMethod = propertyDescriptor.getReadMethod();
                if (readMethod != null) {
                    Class<?> propertyType = propertyDescriptor.getPropertyType();
                    try {
                        Object value1 = readMethod.invoke(p1);
                        Object value2 = readMethod.invoke(p2);
                        //如果是基础类数据
                        if (propertyType.isPrimitive() || baseTypes.contains(propertyType.getName())) {
                            if (!Objects.equals(value1, value2)) {
                                return false;
                            }
                        }
                        //如果是数组
                        else if (propertyType.isArray()) {
                            if (value1 == null && value2 == null) {
                            } else if (value1 != null && value2 != null) {
                                boolean b = equalCollection((Object[]) value1, (Object[]) value2);
                                if (Boolean.FALSE.equals(b)) {
                                    return false;
                                }
                            } else {
                                return false;
                            }
                        }
                        //如果是List、Set
                        else if (Collection.class.isAssignableFrom(propertyType)) {
                            if (value1 == null && value2 == null) {

                            } else if (value1 != null && value2 != null) {
                                boolean b = equalCollection(((Collection) value1).toArray(), ((Collection) value2).toArray());
                                if (Boolean.FALSE.equals(b)) {
                                    return false;
                                }
                            } else if (value1 == null && ((Collection) value2).isEmpty()) {

                            } else if (value2 == null && ((Collection) value1).isEmpty()) {

                            } else {
                                return false;
                            }
                        } else if (Map.class.isAssignableFrom(propertyType)) {
                            if (value1 == null && value2 == null) {

                            } else if (value1 != null && value2 != null) {
                                boolean b = equalMap((Map) value1, (Map) value2);
                                if (Boolean.FALSE.equals(b)) {
                                    return false;
                                }
                            } else {
                                return false;
                            }
                        } else {
                            boolean b = equals(value1, value2);
                            if (Boolean.FALSE.equals(b)) {
                                return false;
                            }
                        }
                    } catch (Exception e) {
                        System.out.println("反射获取对象属性值报错");
                        e.printStackTrace();
                        return false;
                    }
                }
            }
        } else {
            return false;
        }

        return true;
    }

    /**
     * p1中相关属性值，p2中均存在同名属性且相等才返回true（Collection、Map为null或empty认为相同）
     *
     * @param p1
     * @param p2
     * @return
     */
    public static boolean differentObjectEquals(Object p1, Object p2) {
        if (p1 == p2) {
            return true;
        } else if (p1 != null && p2 != null) {
            Class cls1 = p1.getClass();
            Class cls2 = p2.getClass();
            //如果是基础类型数据
            if (baseTypes.contains(cls1.getName())) {
                return Objects.equals(p1, p2);
            }
            PropertyDescriptor[] propertyDescriptors1 = BeanUtils.getPropertyDescriptors(cls1);
            PropertyDescriptor[] propertyDescriptors2 = BeanUtils.getPropertyDescriptors(cls2);
            for (PropertyDescriptor propertyDescriptor1 : propertyDescriptors1) {
                if (propertyDescriptor1.getName().equals("class") || propertyDescriptor1.getName().equals("className")) {
                    continue;
                }
                Method readMethod1 = propertyDescriptor1.getReadMethod();
                PropertyDescriptor propertyDescriptor2 = findSameNameProperty(propertyDescriptors2, propertyDescriptor1);
                if (propertyDescriptor2 == null) {
                    return false;
                }
                Method readMethod2 = propertyDescriptor2.getReadMethod();
                if (readMethod1 != null && readMethod2 != null) {
                    Class<?> propertyType1 = propertyDescriptor1.getPropertyType();
                    Class<?> propertyType2 = propertyDescriptor2.getPropertyType();
                    try {
                        Object value1 = readMethod1.invoke(p1);
                        Object value2 = readMethod2.invoke(p2);
                        //如果是基础类数据
                        if (propertyType1.isPrimitive() || baseTypes.contains(propertyType1.getName())) {
                            if (!Objects.equals(value1, value2)) {
                                return false;
                            }
                        } else if (propertyType1.isArray()) {
                            if (value1 == null && value2 == null) {
                            } else if (value1 != null && value2 != null) {
                                boolean b = equalCollection((Object[]) value1, (Object[]) value2);
                                if (Boolean.FALSE.equals(b)) {
                                    return false;
                                }
                            } else {
                                return false;
                            }
                        } else if (Collection.class.isAssignableFrom(propertyType1)) {
                            //如果是List、Set
                            if (value1 == null && value2 == null) {
                            } else if (value1 != null && value2 != null) {
                                boolean b = equalDiffObjectCollection(((Collection) value1).toArray(), ((Collection) value2).toArray());
                                if (Boolean.FALSE.equals(b)) {
                                    return false;
                                }
                            } else if (value1 == null && ((Collection) value2).isEmpty()) {

                            } else if (value2 == null && ((Collection) value1).isEmpty()) {

                            } else {
                                return false;
                            }
                        } else if (Map.class.isAssignableFrom(propertyType1)) {
                            if (value1 == null && value2 == null) {
                            } else if (value1 != null && value2 != null) {
                                boolean b = equalDiffObjectMap((Map) value1, (Map) value2);
                                if (Boolean.FALSE.equals(b)) {
                                    return false;
                                }
                            } else {
                                return false;
                            }
                        } else {
                            boolean b = differentObjectEquals(value1, value2);
                            if (Boolean.FALSE.equals(b)) {
                                return false;
                            }
                        }
                    } catch (Exception e) {
                        System.out.println("反射获取对象属性值报错");
                        e.printStackTrace();
                        return false;
                    }
                }
            }
        } else {
            return false;
        }
        return true;
    }

    private static PropertyDescriptor findSameNameProperty(PropertyDescriptor[] propertyDescriptors2, PropertyDescriptor propertyDescriptor1) {
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors2) {
            if (propertyDescriptor.getName().equals(propertyDescriptor1.getName())) {
                return propertyDescriptor;
            }
        }
        return null;
    }

    private static boolean equalDiffObjectCollection(Object[] c1, Object[] c2) {
        if (c1.length != c2.length) {
            return false;
        }
        for (int i = 0; i < c1.length; i++) {
            boolean b = differentObjectEquals(c1[i], c2[i]);
            if (Boolean.FALSE.equals(b)) {
                return false;
            }
        }
        return true;
    }

    private static boolean equalDiffObjectMap(Map m1, Map m2) {
        if (m1.size() != m2.size()) {
            return false;
        }
        for (Object key : m1.keySet()) {
            if (!m2.containsKey(key)) {
                return false;
            }
            Object value1 = m1.get(key);
            Object value2 = m2.get(key);
            boolean b = differentObjectEquals(value1, value2);
            if (Boolean.FALSE.equals(b)) {
                return false;
            }
        }
        return true;
    }

    private static boolean equalCollection(Object[] c1, Object[] c2) {
        if (c1.length != c2.length) {
            return false;
        }
        for (int i = 0; i < c1.length; i++) {
            boolean b = equals(c1[i], c2[i]);
            if (Boolean.FALSE.equals(b)) {
                return false;
            }
        }
        return true;
    }

    private static boolean equalMap(Map m1, Map m2) {
        if (m1.size() != m2.size()) {
            return false;
        }
        for (Object key : m1.keySet()) {
            if (!m2.containsKey(key)) {
                return false;
            }
            Object value1 = m1.get(key);
            Object value2 = m2.get(key);
            boolean b = equals(value1, value2);
            if (Boolean.FALSE.equals(b)) {
                return false;
            }
        }
        return true;
    }
}
